﻿Imports Microsoft.VisualBasic
Imports System.IO
Imports System.Web
Imports System.Net
Imports System.Reflection
Imports System.Diagnostics
''' <summary>
''' MAin worker class that parses the IPN request and creates the IPNRequest Object
''' </summary>
''' <remarks></remarks>
Public Class IPN
#Region "Member Declarations"
    Private Const payPalURL As String = "http://{0}/cgi-bin/webscr"
    Private _payPalEnvironment As Environment = Environment.SandBox
    Private _requestLength As String = String.Empty
    Private _requestStatus As PayPalResponse = Nothing
    Private _transactionType As IPNTransactionTypes.TransactionTypes = Nothing
    Private _urlQuery As HttpUrlQuery = Nothing
    Private _rawResponse As String = String.Empty
    Private _rawRequest As String = String.Empty
    Private _writeToFile As Boolean = False
    Private _filePath As String = String.Empty
    Private _logWriter As StreamWriter = Nothing
    Private _log As String = String.Empty
    Private _ipnRequestObject As IPNRequest = Nothing
    Public WithEvents IPNEvent As IPNEvents = New IPNEvents
#End Region
    

#Region "Public Enumerators"
    Public Enum Environment
        SandBox = 1
        BetaSandBox = 2
        Live = 3
    End Enum
    Public Enum PayPalResponse
        VERIFIED
        INVALID
    End Enum
#End Region

#Region "Constructor"
    Public Sub New(ByVal env As Environment)
        _payPalEnvironment = env

    End Sub
#End Region

#Region "Public Properties"
    ''' <summary>
    ''' Gets the current PayPal envirnment to use for the request.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property PayPalHost() As String
        Get
            Dim host As String = String.Empty
            Select Case Me.PayPalEnvironment
                Case Environment.BetaSandBox
                    host = String.Format(payPalURL, "beta-sandbox.paypal.com")
                Case Environment.SandBox
                    host = String.Format(payPalURL, "www.sandbox.paypal.com")
                Case Environment.Live
                    host = String.Format(payPalURL, "www.paypal.com")
            End Select


            Return host
        End Get
    End Property
    ''' <summary>
    ''' Gets or sets the PayPal Environment
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property PayPalEnvironment() As Environment
        Get
            Return _payPalEnvironment
        End Get
        Set(ByVal value As Environment)
            _payPalEnvironment = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the RequestLength that is sent from PayPal
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property RequestLength() As String
        Get
            Return _requestLength
        End Get
        Set(ByVal value As String)
            _requestLength = value
        End Set
    End Property

    ''' <summary>
    ''' Gets the Request Status from PayPal
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>VERIFIED or INVALID</remarks>
    Public ReadOnly Property RequestStatus() As PayPalResponse
        Get
            Return _requestStatus
        End Get
    End Property

    ''' <summary>
    ''' Gets the IPN transaction type (txn_type) from the request.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property TransActionType() As IPNTransactionTypes.TransactionTypes
        Get
            Return _transactionType
        End Get
    End Property

    ''' <summary>
    ''' Gets the URLQuery object
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property URLQuery() As HttpUrlQuery
        Get
            Return _urlQuery
        End Get
    End Property

    ''' <summary>
    ''' Gets the inital or raw querystring response sent back to PayPal
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property RawResponse() As String
        Get
            Return _rawResponse
        End Get
    End Property

    ''' <summary>
    ''' Gets the inital or raw request sent from PayPal
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property RawRequest() As String
        Get
            Return _rawRequest
        End Get
    End Property

    ''' <summary>
    ''' Gets or sets wheather to write a log file for debugging.
    ''' </summary>
    ''' <param name="filePath"></param>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>Used this during debugging to write to a file.</remarks>
    Public Property WriteToFile(ByVal filePath As String) As Boolean
        Get
            Return _writeToFile
        End Get
        Set(ByVal value As Boolean)
            If filePath.Length > 0 Then
                _logWriter = New StreamWriter(filePath, True)
                _writeToFile = value
            End If

        End Set
    End Property

    ''' <summary>
    ''' Gets the IPNRequest object created during parsing.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property IPNRequestObject() As IPNRequest
        Get
            Return _ipnRequestObject
        End Get
    End Property

#End Region



#Region "Public Methods"
    ''' <summary>
    ''' Main method to begin parsing of the request
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub LoadIPN()
        Try
            'Read in the request from PayPal
            LoadResponse()

            If _rawResponse.Length > 0 Then

                'Set All Keys for Request
                _urlQuery = New HttpUrlQuery("?" & _requestLength)

                'Set The PayPal Response Status
                SetStatus()

                'Set the Transaction type
                GetTransActionType()

                ProcessRequest()


            End If
        Catch ex As Exception
            'ToDo: Log EX

        End Try


    End Sub

#End Region
#Region "Private Methods"


    ''' <summary>
    ''' Intercept IPN request from PayPal and 
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub LoadResponse()
        Try
            Dim req As HttpWebRequest = CType(WebRequest.Create(Me.PayPalHost), HttpWebRequest)
            req.Method = "POST"
            req.ContentLength = (Me.RequestLength.Length + 21)
            req.ContentType = "application/x-www-form-urlencoded"
            Dim param() As Byte = HttpContext.Current.Request.BinaryRead(HttpContext.Current.Request.ContentLength)
            Me.RequestLength = System.Text.Encoding.ASCII.GetString(param)
            _rawRequest = Me.RequestLength
            Me.RequestLength = (Me.RequestLength + "&cmd=_notify-validate")
            req.ContentLength = Me.RequestLength.Length
            Dim streamOut As StreamWriter = New StreamWriter(req.GetRequestStream, System.Text.Encoding.ASCII)
            streamOut.Write(Me.RequestLength)
            streamOut.Close()
            Dim streamIn As StreamReader = New StreamReader(req.GetResponse.GetResponseStream)
            _rawResponse = streamIn.ReadToEnd
            streamIn.Close()

        Catch ex As Exception
            'ToDO: Log EX
        End Try

    End Sub

    ''' <summary>
    ''' Set the status property for the request
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub SetStatus()
        _requestStatus = GetEnumeration(Of PayPalResponse)(_rawResponse)
    End Sub

    ''' <summary>
    ''' Set the transaction property to the correct type.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub GetTransActionType()
        If Not _urlQuery Is Nothing Then
            If _urlQuery.Contains("txn_type") Then
                Dim trxTypeName = _urlQuery.Get("txn_type")
                _transactionType = GetEnumeration(Of IPNTransactionTypes.TransactionTypes)(trxTypeName)
            End If
        End If
    End Sub

    ''' <summary>
    ''' Method to dynamically set the enum type for a given enum
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="enumName"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetEnumeration(Of T As New)(ByVal enumName As String) As T
        Try
            Dim retrunEnum As T = New T
            For Each trxType As Integer In [Enum].GetValues(GetType(T))
                Dim tempEnum As T = [Enum].ToObject(GetType(T), trxType)
                Dim tempEnumName As String = tempEnum.ToString
                If enumName = tempEnumName Then
                    retrunEnum = tempEnum
                    Exit For
                End If
            Next

            Return retrunEnum
        Catch ex As Exception
            'ToDo: Catch EX
        End Try

    End Function

    ''' <summary>
    ''' Method to make sure the request is valid and raise transation type events
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub ProcessRequest()
        If Me.RequestStatus = PayPalResponse.VERIFIED Then
            Dim ipn As IPNRequest = LoadIPNRequest(Of IPNRequest)(URLQuery.AllKeys)
            If Not ipn Is Nothing Then
                Select Case Me.TransActionType
                    Case IPNTransactionTypes.TransactionTypes.recurring_payment_profile_created
                        IPNEvent.Recurring_Payment_Profile_CreatedHandler(ipn)
                    Case IPNTransactionTypes.TransactionTypes.recurring_payment
                        IPNEvent.Recurring_PaymentHandler(ipn)
                        _log += "Recurring Payment Event Fired" & vbCrLf
                    Case IPNTransactionTypes.TransactionTypes.recurring_payment_failed
                        IPNEvent.recurring_payment_failedHandler(ipn)
                    Case IPNTransactionTypes.TransactionTypes.recurring_payment_suspended_due_to_max_failed_payment
                        IPNEvent.recurring_payment_suspended_due_to_max_failed_paymentHandler(ipn)
                    Case IPNTransactionTypes.TransactionTypes.web_accept
                        IPNEvent.Web_AcceptHandler(ipn)
                End Select

            End If

        Else

        End If
    End Sub

    ''' <summary>
    ''' Main Method to parse the IPN reuqest and fill the properties.
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="ipnKeys"></param>
    ''' <returns></returns>
    ''' <remarks>A mapping routine.</remarks>
    Private Function LoadIPNRequest(Of T As New)(ByVal ipnKeys() As String) As T

        Dim genericType As Type = GetType(T)

        Dim properties() As PropertyInfo = _
           genericType.GetProperties(BindingFlags.Instance _
           Or BindingFlags.Public)

        Dim obj As T = New T()
        Dim propName As String = String.Empty
        For Each key As String In ipnKeys

            Try
                For Each prop As PropertyInfo In properties
                    Dim columnName As String = prop.Name
                    Dim keyValue As String = URLQuery.Get(key)

                    If keyValue.Length > 0 AndAlso key.ToLower = columnName.ToLower Then
                        Dim value As Object = HttpContext.Current.Server.UrlDecode(URLQuery.Get(key))
                        propName = key
                        Select Case prop.PropertyType.ToString
                            Case GetType(DateTime).ToString
                                prop.SetValue(obj, CDate(FormatPayPalDate(value)), Nothing)
                            Case GetType(Double).ToString
                                prop.SetValue(obj, CDbl(value), Nothing)
                            Case GetType(Integer).ToString
                                prop.SetValue(obj, CInt(value), Nothing)
                            Case Else
                                prop.SetValue(obj, value, Nothing)
                        End Select

                    End If

                Next

            Catch ex As Exception
                'ToDo: Log EX
            End Try

        Next

        Return obj
    End Function

    ''' <summary>
    ''' Method to strip out any itmes that cannot be contained in a System.Date type.
    ''' </summary>
    ''' <param name="dateValue"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function FormatPayPalDate(ByVal dateValue As String) As String
        Return (dateValue.Replace("PDT", "").Replace("PST", "").Replace(",", ""))
    End Function


#End Region

End Class
